#include <cstdint>
#include <gtest/gtest.h>
#include <random>
#include <thread>
#include <tuple>
#include <vector>
#include "log/logqueue.hpp"

class TestLogQueueFixture : public ::testing::Test
{
};

TEST_F(TestLogQueueFixture, TestThreadUnsafeException)
{
    SimpleQueue<int32_t, false> q(-1);
    int32_t e = 1;
    EXPECT_EQ(q.Push(e), -1);
    EXPECT_EQ(q.Pop(e), -1);
}

TEST_F(TestLogQueueFixture, TestThreadUnsafeBase)
{
    int32_t testMaxSize = 12;
    for (int32_t qSize = 1; qSize < testMaxSize; qSize++)
    {
        SimpleQueue<int32_t, false> q(qSize);
        for (int32_t step = 1; step < qSize; step++)
        {
            std::vector<int32_t> vPush;
            std::vector<int32_t> vPop;
            int32_t e;
            int32_t cur = 0;

            while ((cur) < (qSize + step))
            {
                for (int32_t i = 1; i < step; i++)
                {
                    vPush.push_back(step);
                    EXPECT_EQ(q.Push(vPush.back()), 0);
                }

                for (int32_t i = 1; i < step; i++)
                {
                    EXPECT_EQ(q.Pop(e), 0);
                    vPop.push_back(e);
                }

                cur += step;
            }

            EXPECT_EQ(vPush, vPop);
        }
    }
}

TEST_F(TestLogQueueFixture, TestThreadUnsafePerformance)
{
    constexpr int32_t Q_SIZE = 100;
    constexpr int32_t LOOP_COUNT = 10000;
    constexpr int64_t TIMEOUT = 500;  // 240ms
    SimpleQueue<int32_t, false> q(Q_SIZE);

    auto st = std::chrono::steady_clock::now();
    for (int32_t i = 0; i < LOOP_COUNT; i++)
    {
        int32_t e;
        std::vector<int32_t> vPush, vPop;
        for (int32_t j = 0; j < Q_SIZE; j++)
        {
            e = i + j;
            EXPECT_EQ(q.Push(e), 0);
            vPush.push_back(e);
        }

        for (int32_t j = 0; j < Q_SIZE; j++)
        {
            EXPECT_EQ(q.Pop(e), 0);
            vPop.push_back(e);
        }

        EXPECT_EQ(vPush, vPop);
        EXPECT_EQ(q.Pop(e), -1);
    }
    auto ed = std::chrono::steady_clock::now();
    auto dur = ed - st;
    auto durMsCnt = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
    EXPECT_LE(durMsCnt, TIMEOUT);
}

TEST_F(TestLogQueueFixture, TestThreadSafeException)
{
    SimpleQueue<int32_t, true> q(-1);
    std::thread td1([&q] {
        for (int32_t i = 0; i < 100; i++) { EXPECT_EQ(q.Push(1), -1); }
    });

    std::thread td2([&q] {
        int32_t e;
        for (int32_t i = 0; i < 100; i++) { EXPECT_EQ(q.Pop(e), -1); }
    });

    if (td1.joinable())
        td1.join();

    if (td2.joinable())
        td2.join();
}

TEST_F(TestLogQueueFixture, TestThreadSafePerformance)
{
    constexpr int32_t Q_SIZE = 100;
    constexpr int32_t LOOP_COUNT = 1000000;
    constexpr auto TIMEOUT = std::chrono::milliseconds(2000);
    SimpleQueue<int32_t, true> q(Q_SIZE);

    std::thread td1([&] {
        int32_t i = 0;
        auto st = std::chrono::steady_clock::now();
        while (i < LOOP_COUNT)
        {
            auto cur = std::chrono::steady_clock::now();
            auto dur = cur - st;
            if (dur > TIMEOUT)
            {
                FAIL() << "Push Timeout: " << dur.count() << "ns";
            }

            if (q.Push(i) == 0)
            {
                i++;
            }
        }
    });

    std::thread td2([&] {
        int32_t e;
        int32_t i = 0;
        auto st = std::chrono::steady_clock::now();

        while (i < LOOP_COUNT)
        {
            auto cur = std::chrono::steady_clock::now();
            auto dur = cur - st;
            if (dur > TIMEOUT)
            {
                FAIL() << "Pop Timeout: " << dur.count() << "ns";
            }

            if (q.Pop(e) == 0)
            {
                EXPECT_EQ(e, i);
                i++;
            }
        }
    });

    if (td1.joinable())
        td1.join();

    if (td2.joinable())
        td2.join();

    int32_t e;
    EXPECT_EQ(q.Pop(e), -1);
}

enum class TestLogQueueOrderFixtureAction : int32_t
{
    PUSH = 0,
    POP = 1,
};

class TestLogQueueOrderFixture : public ::testing::TestWithParam<std::tuple<int32_t, int32_t, std::vector<TestLogQueueOrderFixtureAction>>>
{
public:
    using Action = TestLogQueueOrderFixtureAction;

    using ParamType = std::tuple<int32_t, int32_t, std::vector<Action>>;

    int32_t GetQSize() const { return std::get<0>(GetParam()); }
    int32_t GetLoopCount() const { return std::max(std::get<1>(GetParam()), 1); }
    const std::vector<Action>& GetActionSeq() const { return std::get<2>(GetParam()); }

    // 生成随机测试参数
    static ParamType GenRandParam(std::tuple<int32_t, int32_t> qsizeRange, std::tuple<int32_t, int32_t> loopCntRange)
    {
        // 获取 Google Test 的随机种子
        int seed = ::testing::UnitTest::GetInstance()->random_seed();
        if (seed == 0)
        {
            seed = time(0);
        }

        std::cout << "Random seed: " << seed << std::endl;

        // 使用该种子初始化随机数生成器
        std::mt19937 rng(seed);
        std::uniform_int_distribution<int32_t> qsizeDistr(std::get<0>(qsizeRange), std::get<1>(qsizeRange));
        std::uniform_int_distribution<int32_t> loopCntDistr(std::get<0>(loopCntRange), std::get<1>(loopCntRange));
        std::uniform_int_distribution<int32_t> actionDistr(0, 1);

        ParamType parameters;
        int32_t qSize = qsizeDistr(rng);
        int32_t loopCnt = loopCntDistr(rng);
        std::vector<Action> actionSeq;
        for (int32_t i = 0; i < loopCnt; i++)
        {
            int32_t action = actionDistr(rng);
            actionSeq.push_back(static_cast<Action>(action));
        }
        parameters = std::make_tuple(qSize, loopCnt, actionSeq);
        return parameters;
    }
};

TEST_P(TestLogQueueOrderFixture, TestQueueOrder)
{
    const int32_t Q_SIZE = GetQSize();
    const int32_t LOOP_COUNT = GetLoopCount();
    constexpr auto TIMEOUT = std::chrono::milliseconds(1200);
    SimpleQueue<int32_t, true> q(Q_SIZE);
    std::vector<int32_t> resSeq;

    auto push_func = [&] {
        int32_t i = 0;
        auto st = std::chrono::steady_clock::now();
        while (i < LOOP_COUNT)
        {
            auto cur = std::chrono::steady_clock::now();
            auto dur = cur - st;
            if (dur > TIMEOUT)
            {
                FAIL() << "Push Timeout: " << dur.count() << "ns";
            }

            if (q.Push(i) == 0)
            {
                i++;
            }
        }
    };

    auto pop_func = [&] {
        int32_t e;
        int32_t i = 0;
        auto st = std::chrono::steady_clock::now();

        while (i < LOOP_COUNT)
        {
            auto cur = std::chrono::steady_clock::now();
            auto dur = cur - st;
            if (dur > TIMEOUT)
            {
                FAIL() << "Pop Timeout: " << dur.count() << "ns";
            }

            if (q.Pop(e) == 0)
            {
                EXPECT_EQ(e, i);
                i++;
                resSeq.push_back(e);
            }
        }
    };

    std::thread td1(push_func);
    std::thread td2(pop_func);

    if (td1.joinable())
        td1.join();

    if (td2.joinable())
        td2.join();

    int32_t e;
    EXPECT_EQ(q.Pop(e), -1);

    ASSERT_EQ(resSeq.size(), LOOP_COUNT);
    for (int32_t i = 0; i < LOOP_COUNT; i++) { EXPECT_EQ(resSeq[i], i); }
}

static std::vector<TestLogQueueOrderFixture::ParamType> TestLogQueueOrderFixtureRandomParam = {
    TestLogQueueOrderFixture::GenRandParam(std::tuple<int32_t, int32_t>(1000, 5000), std::tuple<int32_t, int32_t>(500, 1000)),
    TestLogQueueOrderFixture::GenRandParam(std::tuple<int32_t, int32_t>(500, 1000), std::tuple<int32_t, int32_t>(1000, 5000)),
    TestLogQueueOrderFixture::GenRandParam(std::tuple<int32_t, int32_t>(500, 600), std::tuple<int32_t, int32_t>(500, 600)),
};

INSTANTIATE_TEST_SUITE_P(RandomParameters, TestLogQueueOrderFixture, ::testing::ValuesIn(TestLogQueueOrderFixtureRandomParam));
